/**
 * 
 */
package com.williamoneil.charts.client.util;

import java.util.Date;

import com.google.gwt.i18n.client.DateTimeFormat;

/**
 * Built-in date range provider for various predefined date categories.
 * <p/>
 * Justification for use of deprecated methods on {@link Date}:
 * <br />
 * This class uses deprecated methods of the Date class since it must be able to run on the client side
 * and GWT hasn't emulated the {@link Calendar} class.
 */
@SuppressWarnings("deprecation")
public class CalendarUtil
{
    private static final long MS_PER_SEC = 1000;
    private static final long MS_PER_MIN = MS_PER_SEC * 60;
    private static final long MS_PER_HOUR = MS_PER_MIN * 60;
    private static final long MS_PER_DAY = MS_PER_HOUR * 24;
    private static final long MS_PER_WEEK = MS_PER_DAY * 7;
    
    private CalendarUtil()
    {
    }
    
    public static Date truncateToTime(Date date)
    {
        long time = date.getTime();
        return new Date(time - truncateToDay(date).getTime());
    }
    
    public static Date truncateToDay(Date date)
    {
        return new Date(date.getYear(), date.getMonth(), date.getDate());
    }
    
    public static Date truncateToMonth(Date date)
    {
        return addDays(truncateToDay(date), 1 - date.getDate());
    }
    
    public static final int SUNDAY = 0;
    public static final int MONDAY = 1;
    public static final int TUESDAY = 2;
    public static final int WEDNESDAY = 3;
    public static final int THURSDAY = 4;
    public static final int FRIDAY = 5;
    public static final int SATURDAY = 6;
    
    public static Date toFridaySameWeek(Date date) {
    	int numDays = FRIDAY - date.getDay();
    	if(numDays != 0) {
    		return addDays(date, numDays);
    	}
    	return date;
    }
    
    public static Date toLastDaySameMonth(Date date) {
    	int numDays = getDaysInMonth(date) - date.getDate();
    	if(numDays != 0) {
    		return addDays(date, numDays);
    	}
    	return date;
    }
    
    public static Date truncateToYear(Date date)
    {
        date = truncateToMonth(date);
        date.setMonth(0);
        return date;
    }
    
    public static Date addDays(Date date, int numDays)
    {
        Date newDate = new Date(date.getTime());
        newDate.setDate(date.getDate() + numDays);
        return newDate;
    }
    
    public static Date addWeeks(Date date, int numWeeks)
    {
        return addDays(date, numWeeks * 7);
    }
    
    public static Date addOneWeekDay(Date date)
    {
    	final int day = date.getDay();
    	int addTotalDays = 1;
    	if(day == CalendarUtil.FRIDAY) {
    		addTotalDays = 3;
    	} else if(day == CalendarUtil.SATURDAY) {
    		addTotalDays = 2;
    	}
    	
        Date newDate = new Date(date.getTime());
        newDate.setDate(date.getDate() + addTotalDays);
        return newDate;
    }
    
    
    public static Date addMonths(Date date, int months)
    {
        for (; months < 0; months++)
        {
            Date roundedPriorMonth = addDays(truncateToMonth(date), -1);
            date = addDays(date, -getDaysInMonth(roundedPriorMonth)); 
        }
        for (; months > 0; months--)
        {
            date = addDays(date, getDaysInMonth(date));
        }
        return date;
    }
    
    public static int getDaysInMonth(Date date)
    {
        return getDaysInMonth(date.getYear() + 1900, date.getMonth());
    }
    
    public static int getDaysInMonth(int year, int month)
    {
        switch (month)
        {
            case 1:
                return (((year % 4) == 0 && (year % 100) != 0) || (year % 400) == 0) ? 29 : 28;
    
            case 3:
            case 5:
            case 8:
            case 10:
                return 30;
                
            default:
                return 31;
        }
    }

    /**
     * Determines the number of days between two dates, always rounding up so a difference of 1 day 1 second
     * yield a return value of 2.
     */
    public static int daysDiff(final Date endDate, final Date startDate) {
         return (int)Math.ceil(((double)endDate.getTime() - startDate.getTime()) / MS_PER_DAY);
    }
    
    public static int weeksDiff(final Date endDate, final Date startDate) {
         return (int)Math.ceil(((double)endDate.getTime() - startDate.getTime()) / MS_PER_WEEK);
    }
    
    public static int weekDaysDiff(final Date endDate, final Date startDate) {
    	final int dayOfEndDate = endDate.getDay();
    	final int dayOfStartDate = startDate.getDay();
    	
    	final Date fridayDateForEndDate = toFridaySameWeek(endDate);
    	final Date fridayDateForStartDate = toFridaySameWeek(startDate);
    	
    	final int weeksDiff = weeksDiff(fridayDateForEndDate, fridayDateForStartDate);
    	
    	return (weeksDiff * 5) - (FRIDAY - dayOfEndDate) + (FRIDAY - dayOfStartDate);
    }
    
    public static int monthsDiff(final Date endDate, final Date startDate) {
    	final int yearsDiff = endDate.getYear() - startDate.getYear();
		return (yearsDiff * 12) + endDate.getMonth() - startDate.getMonth();
    }
    
    public static Date toDate(String dateStr) {
    	if (dateStr == null) {
    		return null;
    	}
    	
    	return DateTimeFormat.getShortDateFormat().parse(dateStr);
    }
    
    public static String toString(Date dt) {
    	if(dt == null) {
    		return null;
    	}
    	
    	return getMonthShortName(dt.getMonth()) + " " + dt.getDate() + ", " + (1900+ dt.getYear()); 
    }
    
    private static final String[] monthShortNames = new String[] {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    public static String getMonthShortName(int month) {
    	return monthShortNames[month];
    }
}
